﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using NFit.Core.Errors;
using NFit.Core.Interfaces;

namespace NFit.Core
{
    /// <summary>
    /// A set of helper methods for using reflection
    /// </summary>
    public static class ReflectionHelper
    {
        /// <summary>
        /// Set the value of a property
        /// </summary>
        /// <param name="target"></param>
        /// <param name="propName"></param>
        /// <param name="cell"></param>
        /// <returns></returns>
        public static bool SetPropertyValue(object target, string propName, FixtureCell cell)
        {
            PropertyInfo property = target.GetType().GetProperty(propName);
            if (property == null)
            {
                return false;
            }
            if (cell.ExpectedValue == null)
                cell.ExpectedValue = Convert.ChangeType(cell.Text, property.PropertyType);
            property.SetValue(target, cell.ExpectedValue, null);
            return true;
        }

        /// <summary>
        /// Set the value of a field
        /// </summary>
        /// <param name="target"></param>
        /// <param name="propName"></param>
        /// <param name="cell"></param>
        /// <returns></returns>
        public static bool SetFieldValue(object target, string propName, FixtureCell cell)
        {
            FieldInfo field = target.GetType().GetField(propName);
            if (field == null)
            {
                return false;
            }
            if (cell.ExpectedValue == null)
                cell.ExpectedValue = Convert.ChangeType(cell.Text, field.FieldType);
            field.SetValue(target, cell.ExpectedValue);
            return true;
        }        

        /// <summary>
        /// Get the named method from a type
        /// </summary>
        /// <param name="t"></param>
        /// <param name="methodName"></param>
        /// <returns></returns>
        public static MethodInfo GetMethod(Type t, string methodName)
        {
            MethodInfo method = t.GetMethod(methodName);

            if (method == null)
            {
                throw new MethodNotFoundException(methodName,t);
            }
            return method;
        }

        /// <summary>
        /// Invoke a method on the target
        /// </summary>
        /// <param name="target"></param>
        /// <param name="methodName"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static object RunMethod(object target, string methodName, string[] args)
        {
            return RunMethod(GetMethod(target.GetType(),methodName), target, args);
        }

        /// <summary>
        /// Invoke a method on the target
        /// </summary>
        /// <param name="method"></param>
        /// <param name="target"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public static object RunMethod(MethodInfo method, object target, string[] args)
        {
            object[] parameters = null;
            if (args != null && args.Length > 0)
            {
                parameters = new object[args.Length];
                ParameterInfo[] parametersDef = method.GetParameters();
                for (int i = 0; i < args.Length; i++)
                {
                    parameters[i] = Convert.ChangeType(args[i], parametersDef[i].ParameterType);
                }
            }

            return method.Invoke(target, parameters);
        }

        /// <summary>
        /// Invoke a method
        /// </summary>
        /// <param name="Context"></param>
        /// <param name="methodOwner"></param>
        /// <param name="cellReport"></param>
        /// <param name="cellvalue"></param>
        /// <param name="methodname"></param>
        /// <param name="checkReturn"></param>
        /// <param name="inputs"></param>
        /// <returns></returns>
        public static bool RunMethod(IExecutionContext Context, object methodOwner, FixtureCell cellReport, FixtureCell cellvalue, string methodname, bool checkReturn, string[] inputs)
        {
            string conformedMethodname = Context.NameProvider.ElementName(methodname);
            cellvalue.ActualValue = ReflectionHelper.RunMethod(methodOwner, conformedMethodname, inputs);
            if (checkReturn)
                return CompareCellValues(cellReport, cellvalue);

            return true;
        }

        /// <summary>
        /// Compare ActualValue and ExpectedValue of the cell. 
        /// If ExpectedValue is null, it tries to fill it from the text of the cell
        /// </summary>
        public static bool CompareCellValues(FixtureCell cellReport, FixtureCell cellvalue)
        {
            if (cellvalue.ExpectedValue == null)
            {
                cellvalue.ExpectedValue = Convert.ChangeType(cellvalue.Text, cellvalue.ActualValue.GetType());
            }
            else
            {
                if (cellvalue.ExpectedValue.GetType() != cellvalue.ActualValue.GetType())
                {
                    cellvalue.ExpectedValue = Convert.ChangeType(cellvalue.ExpectedValue, cellvalue.ActualValue.GetType());
                }
            }

            if (!cellvalue.ExpectedValue.Equals(cellvalue.ActualValue))
            {
                cellReport.Status.Success = false;
                cellReport.Status.NestedError = new ValueMismatchError(cellvalue.ExpectedValue, cellvalue.ActualValue);
            }
            else
            {
                cellReport.Status.Success = true;
            }
            return cellReport.Status.Success.Value;
        }

        /// <summary>
        /// Set a field or property value
        /// </summary>
        /// <param name="Context"></param>
        /// <param name="fixture"></param>
        /// <param name="cell"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        public static bool? SetFieldOrProperty(IExecutionContext Context, AbstractFixture fixture, FixtureCell cell, string name)
        {
            string conformed = Context.NameProvider.ElementName(name);

            if (ReflectionHelper.SetPropertyValue(fixture, conformed, cell))
                return null;
            if (ReflectionHelper.SetFieldValue(fixture, conformed, cell))
                return null;

            cell.Status.NestedError = ElementNotFoundError.PropertyOrField(conformed, fixture.GetType().FullName);
            return false;
        }

        /// <summary>
        /// Indicate if the target type has a method named after the input
        /// </summary>
        /// <param name="Context"></param>
        /// <param name="targetType"></param>
        /// <param name="input"></param>
        /// <returns></returns>
        public static bool HasMethod(IExecutionContext Context, Type targetType, string input)
        {
            string conformed = Context.NameProvider.ElementName(input);
            if (targetType.GetMethod(conformed) != null)
                return true;

            return false;
        }
    }
}
